package pl.polidea.AsyncNet;

import android.os.AsyncTask;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.net.URL;
import java.util.*;

public class AsyncNet
{
    
    protected static final int MAX_CONCURRENT_TASKS = 3;
    
    static AsyncNet instance = null;
    
    protected Map<URLConnection, AsyncNetTask<? extends Object>> reqs
            = new HashMap<URLConnection, AsyncNetTask<? extends Object>>();
    protected Map<String, Set<URLConnection>> taggedReqs
            = new HashMap<String, Set<URLConnection>>(); 
    protected LinkedList<URLConnection> reqsQueue
            = new LinkedList<URLConnection>();
    
    private AsyncNet()
    {
    }

    protected synchronized void startRequest(URLConnection req)
    {
        AsyncNetTask<? extends Object> task = reqs.get(req);
        task.active = true;
        task.execute();
    }
    
    protected synchronized void enqueueRequest(URLConnection req)
    {
        reqsQueue.add(req);
    }
    
    protected synchronized URLConnection dequeueRequest()
    {
        return reqsQueue.removeFirst();
    }
    
    protected synchronized void requestFinished(URLConnection req,
            AsyncNetTask<?> task)
    {
        reqs.remove(req);
        taggedReqs.get(task.getTag()).remove(req);
        if (reqs.size() - reqsQueue.size() < MAX_CONCURRENT_TASKS
                && reqsQueue.size() > 0)
            startRequest(dequeueRequest());
    }
    
    // Public interface
    
    public static synchronized AsyncNet instance()
    {
        if (instance == null)
            instance = new AsyncNet();
        return instance;
    }
    
    public synchronized <T> URLConnection addRequest(String tag, String url,
            AsyncNetTaskListener<T> listener, IStreamParser<T> streamParser)
    {
        try
        {
            URLConnection req = new URL(url).openConnection();
            return addRequest(tag, req, listener, streamParser);
        }
        catch (IOException e)
        {
            listener.onFailure(e);
            return null;
        }
    }
    
    public synchronized <T> URLConnection addRequest(String tag,
            URLConnection req, AsyncNetTaskListener<T> listener,
            IStreamParser<T> streamParser)
    {
        if (req == null)
        {
            listener.onFailure(new NullPointerException());
            return null;
        }
        AsyncNetTask<T> task = new AsyncNetTask<T>(tag, req, listener,
                streamParser);
        reqs.put(req, task);
        Set<URLConnection> reqsWithSameTag = taggedReqs.get(tag);
        if (reqsWithSameTag == null)
        {
            reqsWithSameTag = new HashSet<URLConnection>();
            taggedReqs.put(tag, reqsWithSameTag);
        }
        reqsWithSameTag.add(req);
        if (reqs.size() - reqsQueue.size() < MAX_CONCURRENT_TASKS)
            startRequest(req);
        else
            enqueueRequest(req);
        return req;
    }
    
    public synchronized void cancelRequest(URLConnection req)
    {
        AsyncNetTask<? extends Object> task = reqs.get(req);
        if (task == null)
            return;
        if (task.active)
            task.cancel(true);
        else
            reqsQueue.remove(req);
        requestFinished(req, task);
    }
    
    public synchronized void cancelRequestsWithTag(String tag)
    {
        Set<URLConnection> reqsWithTag = taggedReqs.get(tag);
        if (reqsWithTag == null)
            return;
        while (reqsWithTag.isEmpty() == false)
            cancelRequest(reqsWithTag.iterator().next());
    }
    
    // Classes
    
    protected class AsyncNetTask<T> extends AsyncTask<Void, Void, T>
    {
        
        protected final String tag;
        protected final URLConnection request;
        protected final AsyncNetTaskListener<T> listener;
        protected final IStreamParser<T> streamParser;
        protected Exception exception = null;
        public boolean active = false;
        
        public AsyncNetTask(String tag, URLConnection request,
                AsyncNetTaskListener<T> listener, IStreamParser<T> streamParser)
        {
            this.tag = tag;
            this.request = request;
            this.listener = listener;
            this.streamParser = streamParser;
        }
        
        public String getTag()
        {
            return tag;
        }
        
        // AsyncTask
        
        protected T doInBackground(Void... dummy)
        {
            try
            {
                // temporary work-around;
                // more at http://stackoverflow.com/questions/1440957/
                System.setProperty("http.keepAlive", "false");
                request.connect();
                InputStream is = request.getInputStream();
                if (isCancelled())
                    return null;
                T result = streamParser.parse(is);
                is.close();
                return result;
            }
            catch (Exception e)
            {
                exception = e;
            }
            return null;
        }
        
        protected void onPostExecute(T result)
        {
            if (isCancelled())
                return;
            requestFinished(request, this);
            if (exception != null)
                listener.onFailure(exception);
            else
                listener.onSuccess(result);
        }
    
    }
    
}
